/*
 * Copyright (c) 2020, chenshuai (cs4380@163.com).
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.cs.springcloud.common.biz;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cs.springcloud.common.constant.CommonConstant;
import com.cs.springcloud.common.constant.HttpStatusConstant;
import com.cs.springcloud.common.exception.BusinessException;
import com.cs.springcloud.common.msg.TableResultResponse;
import com.cs.springcloud.common.util.BeanUtil;
import com.cs.springcloud.common.vo.ParamQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * BaseBiz 单表基本增删改查
 * < p>
 * 泛型：M 是 mapper 对象，Model 是实体
 * </p>
 *
 * @author cs
 * @version 1.0
 * @date 2020年01月14日
 */
@Component
public abstract class BaseBiz<M extends BaseMapper<Model>, Model> {

    /**
     * 基础mapper接口
     */
    @Autowired
    protected M baseMapper;

    /**
     * 通过主键，获取单条数据
     * <p>
     * 根据查询条件中的属性进行查询，只能有一个返回值，有多个结果会是抛出异常，查询条件使用等号。
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param model 实体
     */
    public Model selectModel(Model model) {
        QueryWrapper<Model> queryWrapper = this.modelBuildQueryWrapper(model);
        return this.baseMapper.selectOne(queryWrapper);
    }

    /**
     * 通过主键，获取单条数据
     * <p>
     * 根据查询条件中的属性进行查询，只能有一个返回值，有多个结果会是抛出异常，查询条件使用等号。
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param query 查询条件
     */
    public Model selectOne(ParamQuery query) {
        return this.selectOne(query, null);
    }

    /**
     * 通过查询条件，获取单条数据
     * <p>
     * 根据查询条件中的属性进行查询，只能有一个返回值，有多个结果是抛出异常，查询条件使用等号。<br/>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param query      查询条件
     * @param properties 指定返回表字段，实体字段名数组
     */
    public Model selectOne(ParamQuery query, String[] properties) {
        QueryWrapper<Model> queryWrapper = new QueryWrapper<>();
        // 指定返回表字段
        if (null != properties && properties.length > 0) {
            queryWrapper.select(properties);
        }
        this.setQueryField(queryWrapper, query);
        return baseMapper.selectOne(queryWrapper);
    }

    /**
     * 获取全部数据
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </P>
     *
     * @param model 查询条件,为 null 则查询所有
     */
    public List<Model> selectListAllByModel(Model model) {
        QueryWrapper<Model> queryWrapper = this.modelBuildQueryWrapper(model);
        return this.baseMapper.selectList(queryWrapper);
    }

    /**
     * 获取全部数据
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </P>
     *
     * @param query 查询条件,为 null 则查询所有
     */
    public List<Model> selectListAll(ParamQuery query) {
        return this.selectFieldListAll(query, null);
    }

    /**
     * 获取指定字段结果集多条数据
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param fields 实体属性名数组，为null则查询所有
     */
    public List<Model> selectFieldListAll(String[] fields) {
        return this.selectFieldListAll(null, fields);
    }

    /**
     * 获取指定字段结果集多条数据
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param query  查询条件,为 null 则查询所有非逻辑删除的数据
     * @param fields 实体属性名数组，为null则查询所有
     */
    public List<Model> selectFieldListAll(ParamQuery query, String[] fields) {
        QueryWrapper<Model> queryWrapper = new QueryWrapper<>();
        if (BeanUtil.isNotEmpty(fields)) {
            queryWrapper.select(fields);
        }
        this.queryParam(queryWrapper, query);
        return baseMapper.selectList(queryWrapper);
    }

    /**
     * 通过实体条件，统计数量
     * <p>
     * 根据实体中的属性查询总数，查询条件使用等号
     * </p>
     *
     * @param model 查询实体
     * @return 匹配总数
     */
    public int selectCount(Model model) {
        QueryWrapper<Model> queryWrapper = this.modelBuildQueryWrapper(model);
        return baseMapper.selectCount(queryWrapper);
    }

    /**
     * 通过实体，新增单条数据
     * <p>
     * 保存一个实体，null的属性不会保存，会使用数据库默认值
     * </p>
     *
     * @param model 实体
     */
    public int insertModel(Model model) {
        return baseMapper.insert(model);
    }

    /**
     * 通过主键id，删除单条数据
     * <p>
     * 根据主键字段进行删除，方法参数必须包含完整的主键属性
     * </p>
     *
     * @param id 主键id
     */
    public int deleteById(Object id) {
        QueryWrapper<Model> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", id);
        return baseMapper.delete(queryWrapper);
    }

    /**
     * 通过实体，删除单条数据
     *
     * @param model 删除条件
     */
    public int deleteModel(Model model) {
        QueryWrapper<Model> queryWrapper = this.modelBuildQueryWrapper(model);
        return baseMapper.delete(queryWrapper);
    }

    /**
     * 通过查询对象，删除单条数据
     *
     * @param queryWrapper 删除条件
     */
    public int delete(QueryWrapper<Model> queryWrapper) {
        return baseMapper.delete(queryWrapper);
    }

    /**
     * 通过实体，更新单条数据
     * <p>
     * 根据主键更新实体全部字段，null值不会更新.
     * 需要更新时，添加在实体类添加
     * TableField(strategy = FieldStrategy.IGNORED)
     * </p>
     *
     * @param model 实体，包含id
     */
    public int updateById(Model model) {
        return baseMapper.updateById(model);
    }

    /**
     * 根据Wrapper条件进行查询
     *
     * @param queryWrapper 查询对象
     */
    public List<Model> selectByWrapper(Wrapper<Model> queryWrapper) {
        return baseMapper.selectList(queryWrapper);
    }

    /**
     * 通过查询参数，获取翻页数据
     *
     * @param query 查询参数对象
     */
    public TableResultResponse<Model> selectTableByParamQuery(ParamQuery query) {
        return this.selectTableByParamQuery(query, null);
    }

    /**
     * 通过查询参数，获取翻页数据
     *
     * @param query  查询参数对象
     * @param fields 实体属性名数组，为null则查询所有
     */
    public TableResultResponse<Model> selectTableByParamQuery(ParamQuery query, String[] fields) {
        QueryWrapper<Model> queryWrapper = new QueryWrapper<>();
        if (BeanUtil.isNotEmpty(fields)) {
            queryWrapper.select(fields);
        }
        this.queryParam(queryWrapper, query);

        Page<Model> page = new Page<>(query.getPage(), query.getLimit());
        IPage<Model> listPage = baseMapper.selectPage(page, queryWrapper);
        List<Model> records = listPage.getRecords();

        if (BeanUtil.isEmpty(records)) {
            return new TableResultResponse<>(0, new ArrayList<>());
        }
        return new TableResultResponse<>(listPage.getTotal(), records);
    }

    /**
     * 通过ParamQuery，设置给queryWrapper查询条件
     * <p>
     * 查询条件都是模糊查询
     * </p>
     *
     * @param queryWrapper 查询对象
     * @param query        查询条件
     */
    public void queryParam(QueryWrapper<Model> queryWrapper, ParamQuery query) {
        Class<Model> clazz = this.getClassByModel();
        // 设置删除参数
        try {
            // 判断是否存在删除字段
            Field field = clazz.getDeclaredField("isDeleted");
            // 存在删除字段，默认添加未删除的条件
            if (field != null) {
                if (BeanUtil.isEmpty(query)) {
                    query = new ParamQuery();
                }
                query.put("is_deleted", CommonConstant.IS_UNDELETED);
            }
        } catch (NoSuchFieldException e) {
            // 没有逻辑删除字段，则不做逻辑删除处理
        }
        // 设置查询参数
        if (BeanUtil.isNotEmpty(query)) {
            Object value;
            for (Map.Entry<String, Object> entry : query.entrySet()) {
                value = entry.getValue();
                String key = this.toLowerCase(entry.getKey());
                // 字符串则做模糊匹配
                if (value instanceof String) {
                    queryWrapper.like(key, entry.getValue().toString() + "%");
                } else {
                    queryWrapper.eq(key, entry.getValue().toString());
                }
            }
        }

    }

    /**
     * 给queryWrapper查询对象，添加查询条件主键（id），逻辑删除（isDeleted）
     * <p>
     * 注意：如果当前实体中存在isDeleted字段，默认添加未删除的条件
     * </p>
     *
     * @param queryWrapper 查询对象
     * @param query        查询条件
     */
    private void setQueryField(QueryWrapper<Model> queryWrapper, ParamQuery query) {
        Class<Model> clazz = this.getClassByModel();
        try {
            // 主键名
            String idField = "id";
            //判断是否存在删除字段
            clazz.getDeclaredField(idField);
            // 是否存在主键
            queryWrapper.eq(idField, query.get(idField));
        } catch (NoSuchFieldException e) {
            throw new BusinessException("缺失查询主键！", HttpStatusConstant.FAIL);
        }

        try {
            clazz.getDeclaredField("isDeleted");
            // 存在删除字段，默认添加未删除的条件
            queryWrapper.eq("is_deleted", CommonConstant.IS_UNDELETED);
        } catch (NoSuchFieldException e) {
            // 不存在逻辑删除字段，则只通过主键查询
        }
    }

    /**
     * 获取当前实体的class对象
     */
    private Class<Model> getClassByModel() {
        ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
        Type[] types = parameterizedType.getActualTypeArguments();
        return (Class<Model>) types[1];
    }

    /**
     * 通过实体对象构建查询对象
     *
     * @param model 实体对象
     * @return QueryWrapper 查询对象
     */
    private QueryWrapper<Model> modelBuildQueryWrapper(Model model) {
        QueryWrapper<Model> queryWrapper = new QueryWrapper<>();
        try {
            Class clazz = model.getClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                // 获取属性的名字
                String name = field.getName();
                if ("isDeleted".equals(name)) {
                    queryWrapper.eq("is_deleted", CommonConstant.IS_UNDELETED);
                    continue;
                } else if (Modifier.isStatic(field.getModifiers())) {
                    // 跳过所有静态属性
                    continue;
                }
                // 私有属性必须设置访问权限
                field.setAccessible(true);
                // 将属性名字的首字母大写
                String filedName = name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
                // 获取属性 get 方法
                Method method = clazz.getMethod("get" + filedName);
                // 调用get方法获取属性值
                Object resultValue = method.invoke(model);
                if (null != resultValue) {
                    queryWrapper.eq(this.toLowerCase(name), resultValue);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return queryWrapper;
    }

    /**
     * 驼峰字段名转换为数据库字段
     *
     * @param fieldName 驼峰字段名
     * @return 数据库字段
     */
    private String toLowerCase(String fieldName) {
        StringBuilder str = new StringBuilder();
        char[] fieldNames = fieldName.toCharArray();
        for (char field : fieldNames) {
            if (Character.isUpperCase(field)) {
                str.append("_");
            }
            str.append(field);
        }
        return str.toString().toLowerCase();
    }
}